#ifndef __CMATRIX4DH_H__
#define __CMATRIX4DH_H__

#include "StdDefs.h"
#include "CMathFixed.h"
#include "CVector4dh.h"



// CMatrix4dh* must maintain casting ability with (fixed*).
// CMatrix4dh may not have virtuals, inherited, or instantiated data.
 

class CMatrix4dh 
{
public:
	friend class CMatrix3d;

public:
	static const fixed LookAtUpVectorTolerance;

public:
	CMatrix4dh();
	CMatrix4dh( const fixed* m );
	CMatrix4dh( fixed m00, fixed m01, fixed m02, 
				fixed m10, fixed m11, fixed m12,
				fixed m20, fixed m21, fixed m22,
				fixed m30, fixed m31, fixed m32,
				int16 nOfArthmeticOpsTriggeringOrientationFixup = 0 ); 
			 // parameter columns are rows in the matrix!
	CMatrix4dh( const CVector4dh* col,
				int16 nOfArthmeticOpsTriggeringOrientationFixup = 0 );
	CMatrix4dh( const CVector4dh& col0, const CVector4dh& col1, 
				const CVector4dh& col2, const CVector4dh& col3,
				int16 nOfArthmeticOpsTriggeringOrientationFixup = 0 ); 
	CMatrix4dh( const CMatrix4dh& m );
	~CMatrix4dh(){}

	CMatrix4dh&			Clear();
	CMatrix4dh&			LoadIdentity();
	CMatrix4dh&			LoadIdentity3x3();
	CMatrix4dh&			Orthonormalize();
	CMatrix4dh&			Scale( fixed si, fixed sj, fixed sk );
	CMatrix4dh&			Scale( const CVector3d& s );
	CMatrix4dh&			RotateX( fixed degrees );
	CMatrix4dh&			RotateY( fixed degrees );
	CMatrix4dh&			RotateZ( fixed degrees );
	CMatrix4dh&			Rotate( fixed degrees, fixed i, fixed j, fixed k );
	CMatrix4dh&			Rotate( fixed degrees, const CVector3d& axis );
	CMatrix4dh&			Translate( fixed i, fixed j, fixed k );
	CMatrix4dh&			Translate( const CVector3d& v );
	CMatrix4dh&			LookAtRHUpJ( fixed i, fixed j, fixed k );
	CMatrix4dh&			LookAtRHUpJ( const CVector3d& direction );
	CMatrix4dh&			LookAtRHUpK( fixed i, fixed j, fixed k );
	CMatrix4dh&			LookAtRHUpK( const CVector3d& direction );

//-jmb: probably dump Transpose for InvRT, etc
	//CMatrix4dh&			Transpose(); // currently scale is not supported
	//CMatrix4dh&			Transpose3x3(); // currently scale is not supported

	CMatrix4dh&			InvertR();
	CMatrix4dh&			InvertRT();
	CMatrix4dh&			InvertIsometricRTS();
//-jmb:

	CMatrix4dh&			SetIgnoreColumn3( const fixed* col );
	CMatrix4dh&			SetIgnoreColumn3( const CVector4dh* col );
	CMatrix4dh&			SetIgnoreColumn3( const CMatrix4dh& m );

	__inline operator	fixed const*() const { return m_col0; }
	__inline operator	fixed*() { return m_col0; }

	CMatrix4dh&			operator=( const fixed* col ); 
	CMatrix4dh&			operator=( const CVector4dh* col );

	CMatrix4dh&			operator*=( const CMatrix4dh& b );

	fixed const&		operator[]( int idx ) const;
	fixed&				operator[]( int idx );

	friend CMatrix4dh	operator*( const CMatrix4dh& a, const CMatrix4dh& b );

	int16				GetNumOfArithmeticOpsSinceLastFixup() const;
	void				SetNumOfArithmeticOpsTriggeringOrientationFixup( int16 val );
	int16				GetNumOfArithmeticOpsTriggeringOrientationFixup() const;

	CVector4dh			m_col0;
	CVector4dh			m_col1;
	CVector4dh			m_col2;
	CVector4dh			m_col3;

protected:
	void				IncrementOperationCount();
	void				ConsiderOrientationFixup();

	boolean				m_orientationFixupIsApplicable;
	uint8				m_reserved[3];
	uint16				m_nOfArithmeticOpsSinceLastFixup;
	uint16				m_nOfArthmeticOpsTriggeringOrientationFixup;
};





inline
CMatrix4dh::CMatrix4dh( const CMatrix4dh& m )
{
	*this = m;
}


inline
CMatrix4dh& CMatrix4dh::Clear()
{
	MEMSET( this, 0, sizeof( CVector4dh ) * 4 );

	m_nOfArithmeticOpsSinceLastFixup = 0;

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::Scale( fixed si, fixed sj, fixed sk )
{
	m_col0 *= si;
	m_col1 *= sj;
	m_col2 *= sk;

	m_orientationFixupIsApplicable = FALSE;

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::Scale( const CVector3d& s )
{
	m_col0 *= s.m_i;
	m_col1 *= s.m_j;
	m_col2 *= s.m_k;

	m_orientationFixupIsApplicable = FALSE;

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::Rotate( fixed degrees, const CVector3d& axis )
{
	return Rotate( degrees, axis.m_i, axis.m_j, axis.m_k );
}


inline
CMatrix4dh&	CMatrix4dh::Translate( const CVector3d& v )
{
	return Translate( v.m_i, v.m_j, v.m_k );
}


//inline
//CMatrix4dh& CMatrix4dh::Transpose()
//{
//	Transpose3x3();
//
//	fixed* a = (fixed*)this;
//	fixed a12 = a[12];
//	fixed a13 = a[13];
//	
//	a[12] = -( CMathFixed::Mul( a[0], a12 ) + CMathFixed::Mul( a[4], a13 ) + CMathFixed::Mul( a[8], a[14] ) );
//	a[13] = -( CMathFixed::Mul( a[1], a12 ) + CMathFixed::Mul( a[5], a13 ) + CMathFixed::Mul( a[9], a[14] ) );
//	a[14] = -( CMathFixed::Mul( a[2], a12 ) + CMathFixed::Mul( a[6], a13 ) + CMathFixed::Mul( a[10], a[14] ) );
//
//	return *this;
//}


inline
CMatrix4dh&	CMatrix4dh::InvertR()
{
	fixed* a = (fixed*)this;
	fixed a1 = a[1]; 
	fixed a2 = a[2]; 
	//fixed a3 = a[3]; 
	fixed a6 = a[6];

	a[1] = a[4]; a[2] = a[8];
	a[4] = a1; a[6] = a[9];
	a[8] = a2; a[9] = a6;

	return *this;
}


inline
CMatrix4dh&	CMatrix4dh::InvertRT()
{
	fixed* a = (fixed*)this;
	fixed a1 = a[1]; 
	fixed a2 = a[2]; 
	//fixed a3 = a[3]; 
	fixed a6 = a[6];

	a[1] = a[4]; a[2] = a[8];
	a[4] = a1; a[6] = a[9];
	a[8] = a2; a[9] = a6;

	fixed a12 = a[12]; fixed a13 = a[13];
	
	a[12] = -( CMathFixed::Mul( a[0], a12 ) + CMathFixed::Mul( a[4], a13 ) + CMathFixed::Mul( a[8], a[14] ) );
	a[13] = -( CMathFixed::Mul( a[1], a12 ) + CMathFixed::Mul( a[5], a13 ) + CMathFixed::Mul( a[9], a[14] ) );
	a[14] = -( CMathFixed::Mul( a[2], a12 ) + CMathFixed::Mul( a[6], a13 ) + CMathFixed::Mul( a[10], a[14] ) );

	return *this;
}


inline
CMatrix4dh&	CMatrix4dh::InvertIsometricRTS()
{
	fixed* a = (fixed*)this;
	fixed a0 = a[0]; 
	fixed a1 = a[1]; 
	fixed a2 = a[2]; 
	//fixed a3 = a[3]; 
	fixed a6 = a[6];	

	fixed s = CMathFixed::Div( CMathFixed::One,
							   CMathFixed::Mul( a0, a0 ) + 
							   CMathFixed::Mul( a1, a1 ) + 
							   CMathFixed::Mul( a2, a2 ) );

	a[0] = CMathFixed::Mul( s, a0 ); 
	a[1] = CMathFixed::Mul( s, a[4] ); 
	a[2] = CMathFixed::Mul( s, a[8] ); 
	a[4] = CMathFixed::Mul( s, a1 ); 
	a[5] = CMathFixed::Mul( s, a[5] ); 
	a[6] = CMathFixed::Mul( s, a[9] );
	a[8] = CMathFixed::Mul( s, a2 );
	a[9] = CMathFixed::Mul( s, a6 );
	a[10] = CMathFixed::Mul( s, a[10] );

	fixed a12 = a[12]; fixed a13 = a[13];
	
	a[12] = -( CMathFixed::Mul( a[0], a12 ) + CMathFixed::Mul( a[4], a13 ) + CMathFixed::Mul( a[8], a[14] ) );
	a[13] = -( CMathFixed::Mul( a[1], a12 ) + CMathFixed::Mul( a[5], a13 ) + CMathFixed::Mul( a[9], a[14] ) );
	a[14] = -( CMathFixed::Mul( a[2], a12 ) + CMathFixed::Mul( a[6], a13 ) + CMathFixed::Mul( a[10], a[14] ) );

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::LookAtRHUpJ( const CVector3d& direction )
{
	return LookAtRHUpJ( direction.m_i, direction.m_j, direction.m_k );
}


inline
CMatrix4dh& CMatrix4dh::LookAtRHUpK( const CVector3d& direction )
{
	return LookAtRHUpK( direction.m_i, direction.m_j, direction.m_k );
}


inline
CMatrix4dh& CMatrix4dh::SetIgnoreColumn3( const fixed* col )
{
	MEMCPY( this, col, sizeof( CVector4dh ) * 3 );	

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::SetIgnoreColumn3( const CVector4dh* col )
{
	MEMCPY( this, col, sizeof( CVector4dh ) * 3 );		

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::operator=( const fixed* m )
{ 
	ASSERT( m ); 

	MEMCPY( this, m, sizeof( CMatrix4dh ) );

	ASSERT( !m_orientationFixupIsApplicable || 
			( m_orientationFixupIsApplicable && m_nOfArthmeticOpsTriggeringOrientationFixup ) );

	return *this;
}


inline
CMatrix4dh& CMatrix4dh::operator=( const CVector4dh* col )
{ 
	ASSERT( col ); 

	MEMCPY( this, col, sizeof( CVector4dh ) * 4 );

	m_nOfArithmeticOpsSinceLastFixup = 0;

	return *this;
}


inline
fixed& CMatrix4dh::operator[]( int idx )
{
	ASSERT( idx < 16 );

	return *( ( (fixed*)this ) + idx );
}


inline
fixed const& CMatrix4dh::operator[]( int idx ) const
{
	ASSERT( idx < 16 );

	return *( ( (fixed*)this ) + idx );
}


inline
int16 CMatrix4dh::GetNumOfArithmeticOpsSinceLastFixup() const
{
	return m_nOfArithmeticOpsSinceLastFixup;
}


inline
void CMatrix4dh::SetNumOfArithmeticOpsTriggeringOrientationFixup( int16 val )
{
	m_nOfArthmeticOpsTriggeringOrientationFixup = val;

	if( m_nOfArthmeticOpsTriggeringOrientationFixup )
		m_orientationFixupIsApplicable = TRUE;
	else
		m_orientationFixupIsApplicable = FALSE;
}


inline
int16 CMatrix4dh::GetNumOfArithmeticOpsTriggeringOrientationFixup() const
{
	return m_nOfArthmeticOpsTriggeringOrientationFixup;
}




#endif // __CMATRIX4DH_H__
